﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using Vortex.Wrapper.Platform;
using Vortex.Debugging;

namespace Vortex.Input {

	/// <summary>
	/// Implementation of IMouse interface for Windows.Forms
	/// </summary>
	public class MouseListener : IMouse, IMessageFilter {
		#region ButtonDownInfo

		///<summary>Contains information when mouse down was raised</summary>
		protected struct ButtonDownInfo {
			private Point _location;
			private DateTime _timeStamp;
			private bool _discarded;

			/// <summary>
			/// Initializes a new instance of the <see cref="ButtonDownInfo"/> struct.
			/// </summary>
			/// <param name="location">The location of mouse down.</param>
			public ButtonDownInfo(Point location) {
				_location = location;
				_timeStamp = DateTime.Now;
				_discarded = false;
			}

			/// <summary>
			/// Gets the location of button down evenet.
			/// </summary>
			/// <value>The location of event.</value>
			public Point Location {
				get { return _location; }
			}

			/// <summary>
			/// Gets the time stamp of button down.
			/// </summary>
			/// <value>The time stamp.</value>
			public DateTime TimeStamp {
				get { return _timeStamp; }
			}

			/// <summary>
			/// Gets a value indicating whether this instance is discarded.
			/// </summary>
			/// <value>
			/// 	<c>true</c> if this instance is discarded; otherwise, <c>false</c>.
			/// </value>
			public bool IsDiscarded {
				get { return _discarded; }
			}

			/// <summary>
			/// Discards this button down info (no click is able).
			/// </summary>
			public void Discard() {
				_discarded = true;
			}

			/// <summary>
			/// Determines whether this instance can click at location.
			/// </summary>
			/// <param name="buttonUpLocation">The button up location.</param>
			/// <returns>
			/// 	<c>true</c> if mouse can click at the specified location; otherwise, <c>false</c>.
			/// </returns>
			public bool CanClick(Point buttonUpLocation) {
				return !_discarded && Math.Abs(buttonUpLocation.X - _location.X) < 8 && Math.Abs(buttonUpLocation.Y - _location.Y) < 8;
			}
		}
		#endregion

		private IntPtr _window;

		private Point _previousLocation;
		private Point _location;
		private int _totalWheelValue;

		private MouseButtons _buttonStates;

		private ButtonDownInfo[] _buttonDownInfo = new ButtonDownInfo[3];

		/// <summary>
		/// Initializes a new instance of the <see cref="MouseListener"/> class.
		/// </summary>
		/// <param name="windowHandle">The window handle to listen.</param>
		public MouseListener(IntPtr windowHandle) {
			_window = windowHandle;
		}


		#region IMessageFilter Members

		public bool PreFilterMessage(ref Message m) {
			if ((WM)m.Msg == WM.MOUSEWHEEL) {
				//wheel will work only if cursor is over window
				if (IsOverWindow) {
					int wheelDelta = WinApi.GetWheelDeltaParam(m.WParam.ToInt32()) / WinApi.WHEEL_DELTA_BASE;
					_totalWheelValue += wheelDelta;
					//raise event if it is possible
					if (null != Wheel) {
						Wheel(
							new MouseWheelActionInfo(wheelDelta, _location, GetKeyStates(ref m))
						);
					}
				}
			} else if (m.HWnd == _window) {
				switch ((WM)m.Msg) {
					case WM.MOUSEMOVE:
						_previousLocation = _location;
						_location = GetLocation(ref m);
						//check should we need raise Move event
						if (null != Move) {
							Move(
								new MouseMoveActionInfo(_location, _previousLocation, GetKeyStates(ref m))
							);
						}
						break;
					case WM.LBUTTONDOWN:
						ProcessMouseDown(MouseButton.Left, MouseButtons.Left, ref m);
						break;
					case WM.RBUTTONDOWN:
						ProcessMouseDown(MouseButton.Right, MouseButtons.Right, ref m);
						break;
					case WM.MBUTTONDOWN:
						ProcessMouseDown(MouseButton.Middle, MouseButtons.Middle, ref m);
						break;
					case WM.LBUTTONUP:
						ProcessMouseUp(MouseButton.Left, MouseButtons.Left, ref m);
						break;
					case WM.RBUTTONUP:
						ProcessMouseUp(MouseButton.Right, MouseButtons.Right, ref m);
						break;
					case WM.MBUTTONUP:
						ProcessMouseUp(MouseButton.Middle, MouseButtons.Middle, ref m);
						break;
					/* I've added calls of mouse down on double click to make listener more responsive... */
					case WM.LBUTTONDBLCLK:
						ProcessMouseDown(MouseButton.Left, MouseButtons.Left, ref m);
						ProcessDoubleClick(MouseButton.Left, ref m);						
						break;
					case WM.RBUTTONDBLCLK:
						ProcessMouseDown(MouseButton.Right, MouseButtons.Right, ref m);
						ProcessDoubleClick(MouseButton.Right, ref m);
						break;
					case WM.MBUTTONDBLCLK:
						ProcessMouseDown(MouseButton.Middle, MouseButtons.Middle, ref m);
						ProcessDoubleClick(MouseButton.Middle, ref m);
						break;
				}
			}

			return false;
		}

		/// <summary>
		/// Processes the mouse up window event.
		/// </summary>
		/// <param name="button">The mouse button.</param>
		/// <param name="buttonFilter">The button filter.</param>
		/// <param name="m">The message.</param>
		private void ProcessMouseUp(MouseButton button, MouseButtons buttonFilter, ref Message m) {
			_buttonStates &= ~buttonFilter;
			MouseButtonActionInfo actionInfo = new MouseButtonActionInfo(button, _location, GetKeyStates(ref m));
			//check should we need raise Move event
			if (null != ButtonUp) ButtonUp(actionInfo);
			if (button == MouseButton.Left && null != LeftButtonUp) LeftButtonUp(actionInfo);
			if (button == MouseButton.Middle && null != MiddleButtonUp) MiddleButtonUp(actionInfo);
			if (button == MouseButton.Right && null != RightButtonUp) RightButtonUp(actionInfo);
			//check generate click event
			ButtonDownInfo info = _buttonDownInfo[(int)button - 1];
			if (info.CanClick(_location)) {
				if (null != Click) Click(actionInfo);
				if (button == MouseButton.Left && null != LeftClick) LeftClick(actionInfo);
				if (button == MouseButton.Middle && null != MiddleClick) MiddleClick(actionInfo);
				if (button == MouseButton.Right && null != RightClick) RightClick(actionInfo);
			}
		}

		/// <summary>
		/// Processes the mouse down event.
		/// </summary>
		/// <param name="button">The mouse button.</param>
		/// <param name="buttonFilter">The button filter.</param>
		/// <param name="m">The message.</param>
		private void ProcessMouseDown(MouseButton button, MouseButtons buttonFilter, ref Message m) {
			_buttonStates |= buttonFilter;
			_buttonDownInfo[(int)button - 1] = new ButtonDownInfo(_location);
			MouseButtonActionInfo actionInfo = new MouseButtonActionInfo(button, _location, GetKeyStates(ref m));
			
			//check should we need raise Move event
			if (null != ButtonDown) { ButtonDown(actionInfo); }
			if (button == MouseButton.Left && null != LeftButtonDown) { LeftButtonDown(actionInfo); }
			if (button == MouseButton.Middle && null != MiddleButtonDown) { MiddleButtonDown(actionInfo); }
			if (button == MouseButton.Right && null != RightButtonDown) { RightButtonDown(actionInfo); }
		}

		/// <summary>
		/// Processes the double click.
		/// </summary>
		/// <param name="button">The button.</param>
		/// <param name="m">The message.</param>
		private void ProcessDoubleClick(MouseButton button, ref Message m) {
			//after double click no click should be performed
			_buttonDownInfo[(int)button - 1].Discard();
			MouseButtonActionInfo actionInfo = new MouseButtonActionInfo(button, _location, GetKeyStates(ref m));
			
			//check should we need raise Move event
			if (null != DoubleClick) DoubleClick(actionInfo);
			if (button == MouseButton.Left && null != LeftDoubleClick) LeftDoubleClick(actionInfo);
			if (button == MouseButton.Middle && null != MiddleDoubleClick) MiddleDoubleClick(actionInfo);
			if (button == MouseButton.Right && null != RightDoubleClick) RightDoubleClick(actionInfo);
		}

		/// <summary>
		/// Gets the location extracted from windows message.
		/// </summary>
		/// <param name="m">The message.</param>
		/// <returns>Location of mouse cursor</returns>
		private Point GetLocation(ref Message m) {
			return new Point(
				WinApi.GetXParam(m.LParam.ToInt32()),
				WinApi.GetYParam(m.LParam.ToInt32())
			);
		}

		/// <summary>
		/// Gets the modification keys from windows message.
		/// </summary>
		/// <param name="m">The message.</param>
		private KeyStates GetKeyStates(ref Message m) {
			return (KeyStates)WinApi.GetKeyStateParam(m.WParam.ToInt32());
		}

		#endregion

		/// <summary>
		/// Sets the cursor location.
		/// </summary>
		/// <param name="location">The location of cursor.</param>
		private void SetCursorLocation(Point location) {
			Cursor.Position = WinApi.ClientToScreen(_window, location);
			_location = location;
			_previousLocation = location;
			
		}

		#region IMouse Members

		/// <summary>
		/// Gets the location of mouse cursor inside a binded window.
		/// </summary>
		/// <value>The location of cursor.</value>
		public Point Location {
			get { return _location; }
			set { SetCursorLocation(value); }
		}

		/// <summary>
		/// Gets the previous location of mouse cursor.
		/// </summary>
		/// <value>The previous location of mouse cursor.</value>
		public Point PreviousLocation {
			get { return _previousLocation; }
		}

		/// <summary>
		/// Gets the delta  of mouse location comparing to last frame.
		/// </summary>
		/// <value>The mouse location delta.</value>
		public Point LocationDelta {
			get { return new Point(_location.X - _previousLocation.X, _location.Y - _previousLocation.Y); }
		}

		/// <summary>
		/// Gets the wheel value since IMouse instance was created.
		/// </summary>
		/// <value>The wheel value.</value>
		public int WheelValue {
			get { return _totalWheelValue; }
		}

		/// <summary>
		/// Gets a value indicating whether mouse cursor is over client area of window.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if mouse cursor is over window; otherwise, <c>false</c>.
		/// </value>
		public bool IsOverWindow {
			get { return WinApi.TestWindowPoint(_window) == HitTestResult.Client; }
		}

		/// <summary>
		/// Gets a value indicating whether left mouse button is down.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if left mouse button is down; otherwise, <c>false</c>.
		/// </value>
		public bool IsLeftDown {
			get { return (_buttonStates & MouseButtons.Left) != MouseButtons.None; }
		}

		/// <summary>
		/// Gets a value indicating whether middle mouse button is down.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if middle mouse button is down; otherwise, <c>false</c>.
		/// </value>
		public bool IsMiddleDown {
			get { return (_buttonStates & MouseButtons.Middle) != MouseButtons.None; }
		}

		/// <summary>
		/// Gets a value indicating whether right mouse button is down.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if right mouse button is down; otherwise, <c>false</c>.
		/// </value>
		public bool IsRightDown {
			get { return (_buttonStates & MouseButtons.Right) != MouseButtons.None; }
		}

		/// <summary>
		/// Triggered when some mouse button is pressed.
		/// </summary>
		public event MouseButtonActionHandler ButtonDown;

		/// <summary>
		/// Triggered when some mouse down is released.
		/// </summary>
		public event MouseButtonActionHandler ButtonUp;

		/// <summary>
		/// Triggered on mouse click.
		/// </summary>
		public event MouseButtonActionHandler Click;

		/// <summary>
		/// Triggered on mouse double click.
		/// </summary>
		public event MouseButtonActionHandler DoubleClick;

		/// <summary>
		/// Triggered when mouse cursor is moved.
		/// </summary>
		public event MouseMoveActionHandler Move;

		/// <summary>
		/// Triggered when mouse wheel is scrolled.
		/// </summary>
		public event MouseWheelActionHandler Wheel;

		/// <summary>
		/// Triggered when left mouse button is pressed.
		/// </summary>
		public event MouseButtonActionHandler LeftButtonDown;

		/// <summary>
		/// Triggered when right mouse button is pressed.
		/// </summary>
		public event MouseButtonActionHandler RightButtonDown;

		/// <summary>
		/// Triggered when middle mouse button is pressed.
		/// </summary>
		public event MouseButtonActionHandler MiddleButtonDown;

		/// <summary>
		/// Triggered when left mouse down is released.
		/// </summary>
		public event MouseButtonActionHandler LeftButtonUp;

		/// <summary>
		/// Triggered when middle mouse down is released.
		/// </summary>
		public event MouseButtonActionHandler MiddleButtonUp;

		/// <summary>
		/// Triggered when right mouse down is released.
		/// </summary>
		public event MouseButtonActionHandler RightButtonUp;

		/// <summary>
		/// Triggered on left mouse button click.
		/// </summary>
		public event MouseButtonActionHandler LeftClick;

		/// <summary>
		/// Triggered on middle mouse button click.
		/// </summary>
		public event MouseButtonActionHandler MiddleClick;

		/// <summary>
		/// Triggered on right mouse button click.
		/// </summary>
		public event MouseButtonActionHandler RightClick;

		/// <summary>
		/// Triggered on left mouse button double click.
		/// </summary>
		public event MouseButtonActionHandler LeftDoubleClick;

		/// <summary>
		/// Triggered on middle mouse button double click.
		/// </summary>
		public event MouseButtonActionHandler MiddleDoubleClick;

		/// <summary>
		/// Triggered on right mouse button double click.
		/// </summary>
		public event MouseButtonActionHandler RightDoubleClick;

		#endregion
	}
}
